home *** CD-ROM | disk | FTP | other *** search
/ Hyper Stacks 1994 May / Hyper Stacks (Pacific HiTech)(1994)[Mac].iso / XCMDS&XFCNS / PhoneDecode / record.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-11-23  |  15.2 KB  |  503 lines  |  [TEXT/KAHL]

  1. /* Copyright ©November 1993, Dale Schaafsma  All rights reserved */
  2. /* main code section for xcmd that recognizes touch tones */
  3. /* parts taken from the following */
  4. /**********
  5.  * Sound Confusion
  6.  *
  7.  *        Copyright © 1992 Bernie Bernstein
  8.  *        9/11/92 - 10/14/92
  9.  *
  10.  *        updated 9/4/93
  11.  ***********/
  12. /***********
  13.  * Fast fourier code from Numerical Recipes in C (altered just slightly)
  14.  **********/
  15. /* Thanks to the following for his contribution of the phone
  16.  * frequencies: brie@brie.andrew.cmu.edu (Brian Harrison)
  17.  *
  18.  * "Dual Tone MultiFrequency"
  19.  * use a monospace font for this table.
  20.  *       1209   1336   1477   1633
  21.  * 697     1      2      3      A
  22.  * 770     4      5      6      B
  23.  * 852     7      8      9      C
  24.  * 941     *      0      #      D
  25.  */
  26. /* define the keypad on a typical phone */
  27. static char keypad[4][3] = {{'1','2','3'},{'4','5','6'},{'7','8','9'},{'#','0','*'}};
  28.  
  29. /**********************
  30.  *        The values outputted by the fft were figured out by using the following
  31.  *        formula where f is the frequency of the partial tone, and x is the output of
  32.  *        the fft:        x = 512*f/(22kHz/2)
  33.  *******************/
  34. /* defining the numbers for the columns and rows (this is what the fft puts out, 
  35.  * more exactly these are integers corresponding to the elements of the array) */
  36. #define R1fft 33  /* adjusted from experimental data (orig. 32) */
  37. #define R2fft 36
  38. #define R3fft 40
  39. #define R4fft 45  /* adjusted from experimental data (orig. 44) */
  40. #define C1fft 57  /* adjusted from experimental data (orig. 56) */
  41. #define C2fft 62
  42. #define C3fft 69
  43. #define C4fft 76
  44. /* define the accuracy the fft has to satisfy to match */
  45. #define kFFTtolerance 0
  46.  
  47. #include    <types.h>
  48. #include    <GestaltEqu.h>
  49. #include    <OSEvents.h>
  50. #include    <Memory.h>
  51. #include    <Sound.h>
  52. #include    <SoundInput.h>
  53. #include    <math.h>
  54. #include    <HyperXcmd.h>
  55.  
  56. #include "record.h"
  57.  
  58. /* declare the global variable */
  59. gGlobalType* gGlobal;
  60. /* extern the phScripts */
  61. extern StringPtr *phScripts[12];
  62.  
  63.  
  64. /***this function taken from soundConfusion by Bernie Bernstein
  65.  * SetUpSounds
  66.  *        SetUpSounds is a routine which takes a buffer handle, sound headers size and
  67.  *        sample rate value and turns it into a snd buffer with the proper header.
  68.  *        It then returns the buffer handle back to the caller.
  69.  ***/
  70. Handle SetUpSounds (Handle Buf, short *HeaderSize, Fixed sampRate)
  71. {
  72.     OSErr        err;
  73.     short        dummy;
  74.     
  75.     Buf = NewHandle(kBuffSize);
  76.     if ((err = MemError()) != noErr || Buf == nil)
  77.         return(nil);
  78.     HLockHi (Buf);
  79.     if ((err = MemError()) != noErr)
  80.         return(nil);
  81.     
  82.     /* parameters are as follows: handle, channels, samplerate, samplesize(8bit), 
  83.                 compression, baseline, size, header)
  84.         size is the # of bytes to be stored (may not be the same as # of samples */
  85.     /* so a more accurate comment is the following: setupsnd header is called twice,
  86.      * the 1st time is to get the size of the header, the 2nd is to insert the size
  87.      * into that header (so keep the 2 calls) */
  88.     /* additional notes, the meter thing is an immediate status, the completion routine
  89.     is called when count bytes are recorded or m milliseconds have passed, the int
  90.     routine is called when the internal buffers of the snd chip are full (this is to
  91.     do processing on the data prior to even getting it committed to main mem, most
  92.     other stuff is as expected, there was no gestalt for play&record */
  93.     /* The call to SetupSndHeader is done twice in order to make sure that the 
  94.     Header size is set correctly.  */
  95.  
  96.     err = SetupSndHeader (Buf, 1, sampRate, 8, 'NONE', 0x3C, 0, HeaderSize);
  97.     if (err != noErr)
  98.         return(nil);
  99.     err = SetupSndHeader (Buf, 1, sampRate, 8, 'NONE', 0x3C, kBuffSize - *HeaderSize, &dummy);
  100.     if (err != noErr)
  101.         return(nil);
  102.         
  103.     return (Buf);
  104. }
  105.  
  106. /****************************************/
  107. /* findAvailableBuff:
  108.  *        Finds the next available buffer
  109.  * returns: the number of it or -1 if there aren't any
  110.  */
  111. int findAvailableBuff()
  112. {
  113.     int i;
  114.  
  115.     for(i = 0; i < gGlobal->NumBuffers; i++)
  116.         if (gGlobal->BuffArray[i].full == false)
  117.             return(i);
  118.     return(-1);
  119. }
  120.  
  121. /****************************************/
  122. /* initToneRecognize:
  123.  *    initializes this whole thing
  124.  * returns: an integer error code (defined in the .h file) -1 is no error
  125.  */
  126. int initToneRecognize()
  127. {
  128.     OSErr        err;
  129.     long        feature;
  130.     long        SoundRefNum;
  131.     Fixed        SampleRate;
  132.     int            NumBuffers,i;
  133.     BuffInfo*    BuffArray;
  134.     SPBPtr        RecordRec;
  135.  
  136.     /* grow the application size to the maximum */
  137. /*    MaxApplZone();*/
  138.     
  139.     /* allocate the global */
  140.     gGlobal = (gGlobalType*)NewPtr(sizeof(gGlobalType));
  141.     if (MemError() != noErr || gGlobal == nil)
  142.         return(kMemory);
  143.     
  144.     /* use Gestalt manager to make sure sound input and output is possible */
  145.     err = Gestalt(gestaltSoundAttr, &feature);
  146.     if (err == noErr)
  147.         {
  148.         if (!TestBit(feature, gestaltHasSoundInputDevice))
  149.             return(kNoInput);
  150.         else
  151.             {
  152.  
  153.             /* does the machine have stereo? */
  154.             gGlobal->Stereo = TestBit(feature, gestaltStereoCapability);
  155.  
  156.             /* it was claimed that the play and record bit was created in
  157.             /* system 7.1, but I couldn't find it.  I used a program (Parrot
  158.             /* by Bernie Bernstein) and it would record and play simultaneously,
  159.             /* so I looked at the output of the gestalt call, and the only bits 
  160.             /* on were in bits 0 through 5, and as bits 0 through 5 are
  161.             /* already defined in the gestalt header file (not as the 
  162.             /* playAndRecord bit) I am assuming that if we have stereo we
  163.             /* have the better Sound Chip and so can Play and Record at the
  164.             /* same time */
  165.             gGlobal->PlayAndRecord = gGlobal->Stereo;
  166.             }
  167.         }
  168.     else
  169.         return(kGestaltFailed);
  170.  
  171.     /* open the sound input device */
  172.     err = SPBOpenDevice (nil, siWritePermission, &SoundRefNum);
  173.     if (err != noErr)
  174.         return(kOpeningDevice);
  175.     gGlobal->SoundRefNum = SoundRefNum;
  176.     
  177.     /* Get the sample rate information for the snd header */
  178.     err = SPBGetDeviceInfo (SoundRefNum,siSampleRate, (Ptr) &SampleRate);
  179.     if (err != noErr)
  180.         return(kGettingRate);
  181.  
  182.  
  183.     /* start allocating buffers (leave room for an extra buffer, and other mem) */
  184.     /* first find out how many buffers we can allocate */
  185.     NumBuffers = ((FreeMem() - kExtraMem) >>kBuffSizePower) - 1;
  186.     if (NumBuffers < 1)
  187.         return(kMemory);
  188.  
  189.     /* allocate the array of pointers */
  190.     BuffArray = (BuffInfo*)NewPtr(sizeof(BuffInfo)*NumBuffers);
  191.     if ((err = MemError()) != noErr || BuffArray == nil)
  192.         return(kMemory);
  193.     gGlobal->BuffArray = BuffArray;
  194.     gGlobal->NumBuffers = NumBuffers;
  195.  
  196.     /* allocate the sound buffers, and initialize the BuffInfo structures */
  197.     for (i=0; i<NumBuffers; i++)
  198.         {
  199.         BuffArray[i].buffer = SetUpSounds(BuffArray[i].buffer, &BuffArray[i].headerlength, SampleRate);
  200.         if (BuffArray[i].buffer == nil)
  201.             return(kBufSetup);
  202.         BuffArray[i].buffSize = kBuffSize - BuffArray[i].headerlength;
  203.         BuffArray[i].full = false;
  204.         }
  205.  
  206.     /* build the RecordRec pointer and fill in the fields */
  207.     RecordRec = (SPBPtr) NewPtr(sizeof (SPB));
  208.     if ((err = MemError()) != noErr || RecordRec == nil)
  209.         return(kMemory);
  210.     gGlobal->RecordRec = RecordRec;
  211.  
  212.     RecordRec->inRefNum = SoundRefNum;
  213.     RecordRec->count =  kBuffSize - BuffArray[0].headerlength;
  214.     RecordRec->milliseconds = 0;
  215.     RecordRec->bufferLength = kBuffSize - BuffArray[0].headerlength;
  216.     RecordRec->bufferPtr = (Ptr) ((*BuffArray[0].buffer) + BuffArray[0].headerlength);
  217.     RecordRec->completionRoutine = (ProcPtr) &myRecordCallback;
  218.     RecordRec->interruptRoutine = nil;
  219.     RecordRec->userLong = (long)gGlobal;
  220.     RecordRec->error = 0;
  221.     RecordRec->unused1 = 0;
  222.  
  223.     /* allocate the array for the fft */
  224.     gGlobal->fftData = (long double *)NewPtr(kSample*sizeof(long double));
  225.     if ((err = MemError()) != noErr || gGlobal->fftData == nil)
  226.         return(kMemory);
  227.  
  228.     /* finish setting up the global var */
  229.     gGlobal->Error = kNoError;
  230.     gGlobal->FullBuffer = false;
  231.     gGlobal->RecordingBuff = -1;
  232.     gGlobal->Quit = false;
  233.     gGlobal->FirstTime = true;
  234.  
  235.     return(kNoError);
  236. }
  237.  
  238.  
  239. /****************************************/
  240. /* myRecordCallback:
  241.  *    function called when a buffer being recorded into becomes full
  242.  * returns: an integer error code through the global
  243.  */
  244. pascal void myRecordCallback (SPBPtr inParamPtr)
  245. {
  246.     gGlobalType* CallbackGlobal;
  247.     
  248.     CallbackGlobal = (gGlobalType*)inParamPtr->userLong;
  249.     CallbackGlobal->FullBuffer = true;
  250.  
  251.     if (inParamPtr->error != noErr && !CallbackGlobal->Quit)
  252.         gGlobal->Error = kCallbackError;
  253.     return;
  254. }
  255.  
  256.  
  257. /****************************************/
  258. /* finishToneRecognize:
  259.  *    clean up, and deallocate all of the memory 
  260.  * returns: the error number (defined in the .h file)
  261.  */
  262. int finishToneRecognize()
  263. {
  264.     int i;
  265.     SndCommand        mycmd;
  266.  
  267.     /* have we already cleaned up? */
  268.     if (!gGlobal)
  269.         return;
  270.  
  271.     if (gGlobal->SoundRefNum != 0)
  272.         {
  273.         /* stop recording and close the input device */
  274.         if (SPBStopRecording(gGlobal->SoundRefNum) != noErr)
  275.             return(kStopRecord);
  276.         if (SPBCloseDevice(gGlobal->SoundRefNum) != noErr)
  277.             return(kStopRecord);
  278.         }
  279.         
  280.     /* deallocate the buffers */
  281.     for (i = 0; i < gGlobal->NumBuffers; i++)
  282.         if (gGlobal->BuffArray[i].buffer)
  283.                 {
  284.                 HUnlock(gGlobal->BuffArray[i].buffer);
  285.                 DisposeHandle(gGlobal->BuffArray[i].buffer);
  286.                 }
  287.  
  288.     /* deallocate the global structure */
  289.     DisposePtr((Ptr) gGlobal->fftData);
  290.     DisposePtr((Ptr) gGlobal->BuffArray);
  291.     DisposePtr((Ptr) gGlobal->RecordRec);
  292.     DisposePtr((Ptr) gGlobal);
  293. }
  294.  
  295.  
  296. /************************************/
  297. /* toneRecognize:
  298.  *        This routine is called using idle time, and records into the buffers
  299.  *      Then the Fast Fourier Transform (FFT) is run on the buffers to determine
  300.  *        What button was pressed on the phone.
  301.  *      By the nature of how we are processing the buffer's, the generated
  302.  *        numbers will be in sequence
  303.  * returns: either a pointer to a linked list or an error number
  304.  */
  305. long toneRecognize(XCmdPtr paramPtr)
  306. {
  307.     int filledBuff,recordingBuff;
  308.     
  309.     /* if this is not the first time through and we don't have a full buffer exit */
  310.     if (!gGlobal->FullBuffer && !gGlobal->FirstTime)
  311.         return(kNoError);
  312.  
  313.     /* if we have a full buffer or this is the first time through then start recording */
  314.     if (gGlobal->FullBuffer || gGlobal->FirstTime)
  315.         {
  316.         /* save which buffer got filled */
  317.         filledBuff = gGlobal->RecordingBuff;
  318.         if (filledBuff != -1)
  319.             gGlobal->BuffArray[filledBuff].full = true;
  320.         
  321.         /* asynchronously start recording the next available buffer */
  322.         recordingBuff = findAvailableBuff();
  323.         if (recordingBuff == -1)
  324.             return(kBuffersFull);
  325.         gGlobal->RecordingBuff = recordingBuff;
  326.         HUnlock(gGlobal->BuffArray[recordingBuff].buffer);
  327.         SetHandleSize(gGlobal->BuffArray[recordingBuff].buffer,kBuffSize);
  328.         HLockHi(gGlobal->BuffArray[recordingBuff].buffer);
  329.         gGlobal->RecordRec->bufferPtr = (*(gGlobal->BuffArray[recordingBuff].buffer)) +
  330.                             gGlobal->BuffArray[recordingBuff].headerlength;
  331.         gGlobal->FullBuffer = false;
  332.         if (SPBRecord (gGlobal->RecordRec, true) != noErr)
  333.             return(kRecord);
  334.         
  335.         /* if it isn't the first time, process this buffer */
  336.         if (!gGlobal->FirstTime)
  337.             {
  338.             /* run the FFT on the non-quiet sections of the buffer and 
  339.              * pass back the sequence of numbers */
  340.             ParseBuff(gGlobal->BuffArray[filledBuff],paramPtr);
  341.             
  342.             /* mark the buffer as recordable */
  343.             gGlobal->BuffArray[filledBuff].full = false;
  344.             }
  345.         gGlobal->FirstTime = false;
  346.         return(kNoError);
  347.         }
  348. }
  349.  
  350. /* ParseBuff:
  351.  * a function to determine where the sounds occur and run the fft only on that
  352.  * section
  353.  * returns: one char number that was found */
  354. char ParseBuff(BuffInfo buffer,XCmdPtr paramPtr)
  355. {
  356.     long i,j;
  357.     int k = 0,size = kNumsInSample - 1;
  358.     char num;
  359.     unsigned char *tmpbuff,*tmpbuff2;
  360.     long double *data;
  361.  
  362.     data = gGlobal->fftData;
  363.     data[0] = 0.0;
  364.     
  365.     /* dereference the pointer */
  366.     tmpbuff = *(buffer.buffer);
  367.     
  368.     /* start searching the buffer */
  369.     for (i = buffer.headerlength; i < buffer.headerlength + buffer.buffSize; i++)
  370.         {
  371.         if (Abs(0x80 - (unsigned char)tmpbuff[i]) > kThreshold)/* if above threshold */
  372.             {
  373.             /* make sure that there are enough bytes to do a kSample segment */
  374.             if ((i+kSampleDiv2) > (buffer.headerlength + buffer.buffSize))
  375.                 break;  /* not enough */
  376.             /* found beginning of a sound just take the next kSample bytes */
  377.             for(k = 1; k< kSample; k++) {
  378.                 data[k++] = (long double) tmpbuff[i++];  /* real part */
  379.                 data[k] = 0.0;  /* complex part */
  380.                 }
  381.             /* i got incremented by sampleDiv2 size */
  382.             /* fft the sound storing the resulting key */
  383.             nfft(data,1024,1);
  384.             num = findmax(data,1024,paramPtr);
  385.             }
  386.         }
  387.     return num;
  388. }
  389.  
  390.         
  391. /*******************
  392.  *  FFT section
  393.  *    the numerical recipes fft #1 
  394.  *    assumes that the isign is 1
  395.  */
  396. #define SWAP(a,b) tempr=(a);(a)=(b);(b)=tempr
  397.  
  398. /* replaces data by its discrete Fourier transform if isign=1
  399.  * replaces data by nn times its inverse Fourier if isign=-1
  400.  * data is a complex array of length nn, input as a real array[1..2*nn]
  401.  * the fact that nn is an integer power of two is not checked  */
  402. void nfft(long double data[],unsigned long nn,int isign)
  403. {
  404.     unsigned long n,mmax,m,j,istep,i;
  405.     long double wtemp,wr,wpr,wpi,wi,theta;
  406.     long double tempr,tempi;
  407.  
  408.     n=nn <<1;
  409.     j=1;
  410.     for (i=1;i<n;i+=2) {    /* bit reversal segment */
  411.     if (j >i) {
  412.         SWAP(data[j],data[i]);    /* exchange the complex nums */
  413.         SWAP(data[j+1],data[i+1]);
  414.         }
  415.     m=n >> 1;
  416.         while (m >= 2 && j > m) {
  417.         j -= m;
  418.         m >>= 1;
  419.     }
  420.     j += m;
  421.     }
  422.     mmax=2;        /* here begins the danielson-lanczos sec. */
  423.     while (n > mmax) {  /* oouter loop exec. log2 nn times */
  424.     istep=2*mmax;
  425.     theta=6.28318530717959/mmax;    /* init for trig reoccurance (originally *isign) */
  426.     wtemp=sin(0.5*theta);
  427.     wpr = -2.0*wtemp*wtemp;
  428.     wpi=sin(theta);
  429.     wr=1.0;
  430.     wi=0.0;
  431.     for (m=1;m<mmax;m+=2) {    /* here are 2 nested inner loops */
  432.         for (i=m;i<=n;i+=istep) {
  433.         j=i+mmax;    /* this is the danielson-lanczos formula: */
  434.         tempr=wr*data[j]-wi*data[j+1];
  435.         tempi=wr*data[j+1]+wi*data[j];
  436.         data[j]=data[i]-tempr;
  437.         data[j+1]=data[i+1]-tempi;
  438.         data[i] += tempr;
  439.         data[i+1] += tempi;
  440.         }        /* trig recurrance */
  441.         wr=(wtemp=wr)*wpr-wi*wpi+wr;
  442.         wi=wi*wpr+wtemp*wpi+wi;
  443.     }
  444.     mmax=istep;
  445.     }
  446. }
  447.  
  448.  
  449. /* find the row and column in the fourier-ed data, and return a char based on them */
  450. char findmax(long double Data[],unsigned long Samples,XCmdPtr paramPtr)
  451. {
  452. int row,col;
  453.  
  454.     /* determine the row */
  455.     if (sqrt(Data[R1fft*2-1]*Data[R1fft*2-1] + Data[R1fft*2]*Data[R1fft*2]) > kFFTthreshold)
  456.         row =1;
  457.     else if (sqrt(Data[R2fft*2-1]*Data[R2fft*2-1] + Data[R2fft*2]*Data[R2fft*2]) > kFFTthreshold)
  458.         row = 2;
  459.     else if (sqrt(Data[R3fft*2-1]*Data[R3fft*2-1] + Data[R3fft*2]*Data[R3fft*2]) > kFFTthreshold)
  460.         row = 3;
  461.     else if (sqrt(Data[R4fft*2-1]*Data[R4fft*2-1] + Data[R4fft*2]*Data[R4fft*2]) > kFFTthreshold)
  462.         row = 4;
  463.     else return kBadKey;
  464.  
  465.     /* determine the column */
  466.     if (sqrt(Data[C1fft*2-1]*Data[C1fft*2-1] + Data[C1fft*2]*Data[C1fft*2]) > kFFTthreshold)
  467.         col =1;
  468.     else if (sqrt(Data[C2fft*2-1]*Data[C2fft*2-1] + Data[C2fft*2]*Data[C2fft*2]) > kFFTthreshold)
  469.         col = 2;
  470.     else if (sqrt(Data[C3fft*2-1]*Data[C3fft*2-1] + Data[C3fft*2]*Data[C3fft*2]) > kFFTthreshold)
  471.         col = 3;
  472.     else return kBadKey;
  473.  
  474.     /* invoke the proper handler */
  475.     switch (row) {
  476.         case 1:  /* 1 2 3 */
  477.             RunHandler(paramPtr,phScripts[col]);
  478.             return keypad[row][col];
  479.             break;
  480.         case 2:  /* 4 5 6 */
  481.             RunHandler(paramPtr,phScripts[col+3]);
  482.             return keypad[row][col];
  483.             break;
  484.         case 3:  /* 7 8 9 */
  485.             RunHandler(paramPtr,phScripts[col+6]);
  486.             return keypad[row][col];
  487.             break;
  488.         case 4:  /* # 0 * */
  489.             switch (col) {
  490.                 case 1:
  491.                     RunHandler(paramPtr,phScripts[10]);
  492.                     return '*';
  493.                 case 2:
  494.                     RunHandler(paramPtr,phScripts[0]);
  495.                     return '0';
  496.                 case 3:
  497.                     RunHandler(paramPtr,phScripts[11]);
  498.                     return '#';
  499.                 }
  500.             break;
  501.         }
  502.     return kBadKey;
  503. }